﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Security;
using System.Diagnostics;
using System.Globalization;

namespace Hallenberg.Web
{
    public class MemoryRoleProvider : RoleProvider
    {
        List<string> m_Roles;
        Dictionary<string, List<string>> m_RoleUsers;

        public MemoryRoleProvider()
        {
            Reset();
        }

        public void Reset()
        {
            m_Roles = new List<string>();
            m_RoleUsers = new Dictionary<string, List<string>>();
        }

        public override void AddUsersToRoles(string[] usernames, string[] roleNames)
        {
            Debug.Assert(usernames != null);
            if (usernames == null)
            {
                throw new ArgumentNullException("usernames");
            }

            Debug.Assert(roleNames != null);
            if (roleNames == null)
            {
                throw new ArgumentNullException("roleNames");
            }

            foreach (string username in usernames)
            {
                MembershipUser user = Membership.GetUser(username);
                if (user == null)
                {
                    throw new InvalidOperationException("User [" + username + "] does not exist");
                }

                if (!m_RoleUsers.ContainsKey(username))
                {
                    m_RoleUsers.Add(username, new List<string>());
                }

                int unknownRoleCount = roleNames.Count(r => !m_Roles.Contains(r));

                Debug.Assert(unknownRoleCount == 0, "If count > 0, the role is not defined in m_Roles");
                if (unknownRoleCount > 0)
                {
                    throw new InvalidOperationException("One or more provided role names are not defined as a role");
                }

                m_RoleUsers[username].AddRange(roleNames);
            }
        }

        public override string ApplicationName { get; set; }

        public override void CreateRole(string roleName)
        {
            if (!m_Roles.Contains(roleName))
            {
                m_Roles.Add(roleName);
            }
        }

        public override bool DeleteRole(string roleName, bool throwOnPopulatedRole)
        {
            Debug.Assert(!string.IsNullOrEmpty(roleName));
            if (string.IsNullOrEmpty(roleName))
            {
                throw new ArgumentNullException("roleName");
            }

            var usersInRoleQuery = from roleUser in m_RoleUsers
                                   where roleUser.Value.Contains(roleName)
                                   select roleName;

            int usersInRoleCount = usersInRoleQuery.Count();

            if (throwOnPopulatedRole && usersInRoleCount > 0)
            {
                throw new InvalidOperationException("The role is in use");
            }

            foreach (var user in usersInRoleQuery)
            {
                m_RoleUsers[user].Remove(roleName);
            }

            return m_Roles.Remove(roleName);
        }

        public override string[] FindUsersInRole(string roleName, string usernameToMatch)
        {
            Debug.Assert(!string.IsNullOrEmpty(roleName));
            if (string.IsNullOrEmpty(roleName))
            {
                throw new ArgumentNullException("roleName");
            }

            Debug.Assert(!string.IsNullOrEmpty(usernameToMatch));
            if (string.IsNullOrEmpty(usernameToMatch))
            {
                throw new ArgumentNullException("usernameToMatch");
            }

            var usersInRoleQuery = from roleUser in m_RoleUsers
                                   where roleUser.Key.StartsWith(usernameToMatch, StringComparison.CurrentCultureIgnoreCase) && roleUser.Value.Contains(roleName)
                                   select roleUser.Key;

            return usersInRoleQuery.ToArray();

        }

        public override string[] GetAllRoles()
        {
            return m_Roles.ToArray();
        }

        public override string[] GetRolesForUser(string username)
        {
            Debug.Assert(!string.IsNullOrEmpty(username));
            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            var roleQuery = from role in m_RoleUsers
                            where string.Compare(role.Key, username, true, CultureInfo.CurrentCulture) == 0
                            select role.Value;

            // It is perfectly legal to have a user without roles.

            List<string> roles = roleQuery.FirstOrDefault();
            return roles != null ? roles.ToArray() : new string[] { };
        }

        public override string[] GetUsersInRole(string roleName)
        {
            throw new NotImplementedException();
        }

        public override bool IsUserInRole(string username, string roleName)
        {
            Debug.Assert(!string.IsNullOrEmpty(username));
            if (string.IsNullOrEmpty(username))
            {
                throw new ArgumentNullException("username");
            }

            Debug.Assert(!string.IsNullOrEmpty(roleName));
            if (string.IsNullOrEmpty(roleName))
            {
                throw new ArgumentNullException("roleName");
            }

            var userRoleQuery = from userRole in m_RoleUsers
                                where string.Compare(userRole.Key, username, true, CultureInfo.CurrentCulture) == 0 && userRole.Value.Contains(roleName)
                                select userRole.Key;

            return userRoleQuery.Count() == 1;
        }

        public override void RemoveUsersFromRoles(string[] usernames, string[] roleNames)
        {
            var userQuery = from userRole in m_RoleUsers
                            where usernames.Contains(userRole.Key) &&
                                (from rname in userRole.Value where roleNames.Contains(rname) select rname).Count() != 0
                            select userRole;

            foreach (var foundUser in userQuery)
            {
                foreach (string roleName in roleNames)
                {
                    foundUser.Value.Remove(roleName);
                }
            }
        }

        public override bool RoleExists(string roleName)
        {
            return m_Roles.Contains(roleName);
        }
    }
}
